﻿using System.Linq;
using JetBrains.Annotations;

// ReSharper disable CheckNamespace
namespace System.Collections.Generic //Based on namespace of IEnumerable<T>
// ReSharper restore CheckNamespace
{
    public static class EnumerableExtenstions
    {
        public static bool AllX<T>(this IEnumerable<T> source, Func<T, int, bool> predicate)
        {
            int i = 0;
            return source.All(t => predicate(t, i++));
        }

        public static IEnumerable<K> SafeSelectX<T, K>([NotNull]this IEnumerable<T> source, [NotNull]Func<T, K> converter, [NotNull]Action<Exception> errorHandler)
        {
            return source.Select(x =>
            {
                try
                {
                    return converter(x);
                }
                catch (Exception ex)
                {
                    errorHandler(ex);
                    return default(K);
                }
            });
        }
    
        [NotNull]
        public static IEnumerable<T> SafeWhereX<T>([NotNull]this IEnumerable<T> source, [NotNull]Predicate<T> predicate, [NotNull]Action<Exception> errorHandler)
        {
            return source.Where(x =>
            {
                try
                {
                    return predicate(x);
                }
                catch (Exception ex)
                {
                    errorHandler(ex);
                    return false;
                }
            });
        }

        [NotNull]
        public static IEnumerable<T> FollowedByUnlessNullX<T>(this IEnumerable<T> source, T suffix) where T : class
        {
            foreach (T enumerable in source)
            {
                yield return enumerable;
            }
            if (suffix != null)
            {
                yield return suffix;
            }
        }
    
        [NotNull]
        public static IEnumerable<T> FollowedByX<T>(this IEnumerable<T> source, T suffix)
        {
            foreach (T enumerable in source)
            {
                yield return enumerable;
            }
            yield return suffix;
        }

        public static int FirstIndexOfX<T>(this IEnumerable<T> source, Predicate<T> pred)
        {
            int i = 0;
            foreach (T item in source)
            {
                if (pred(item))
                {
                    return i;
                }
                i++;
            }
            return -1;
        }

        public static T FirstOrDefaultX<T>(this IEnumerable<T> source, T defaultValue)
        {
            if (source.Count() == 0)
            {
                return defaultValue;
            }
            return source.First();
        }

        public static IEnumerable<T> AsNewEnumerableX<T>(this IEnumerable<T> source)
        {
            return source.Select(x => x);
        }

        public static IEnumerable NotOfTypeX<T>(this IEnumerable source)
        {
            return source.Cast<object>().Where(x => !(x is T));
        }

        //public static IEnumerable<TOriginalEnumerable> NotOfTypeX<TOriginalEnumerable, TToExclude>(this IEnumerable<TOriginalEnumerable> source)
        //{
        //    return source.NotOfTypeX<TToExclude>().Cast<TOriginalEnumerable>();
        //}


        public static bool HasMoreThanOneElementX<T>(this IEnumerable<T> source)
        {
            return source.Skip(1).Any();
        }
        public static bool HasMoreThanOneElementX(this IEnumerable source)
        {
            var enumerator = source.GetEnumerator();
            return enumerator.MoveNext() ? enumerator.MoveNext() : false;
        }

        public static bool HasElementsX<T>(this IEnumerable<T> source)
        {
            return source.Any();
        }
        public static bool HasElementsX(this IEnumerable source)
        {
            return source.GetEnumerator().MoveNext();
        }
        public static bool IsEmptyX<T>(this IEnumerable<T> source)
        {
            return source.Any() == false;
        }
        public static bool IsEmptyX(this IEnumerable source)
        {
            return source.GetEnumerator().MoveNext() == false;
        }
        public static IEnumerable<T> ExceptNullsX<T>(this IEnumerable<T> source) where T : class
        {
            return source.Where(x => x != null);
        }

        public static IEnumerable<T> ExceptX<T>(this IEnumerable<T> source, T excludedMember)
        {
            return source.Except(excludedMember.AsEnumerableEmptyIfNullX());
        }
        public static IEnumerable<T> ExceptX<T>(this IEnumerable<T> source, T excludedMember, IEqualityComparer<T> comparer)
        {
            return source.Except(excludedMember.AsEnumerableEmptyIfNullX(), comparer);
        }

        public static T MinOrValueIfEmptyX<T>(this IEnumerable<T> source, T value)
        {
            if (source.IsEmptyX())
            {
                return value;
            }
            return source.Min();
        }


        //this implemntation was copied from the generic enumerable implemntation
        //public static int CountX(this IEnumerable source)
        //{
        //    if (source == null)
        //    {
        //        throw new ArgumentNullException("source");
        //    }
        //    ICollection is2 = source as ICollection;
        //    if (is2 != null)
        //    {
        //        return is2.Count;
        //    }
        //    int num = 0;
        //    if (source is IDisposable)
        //    {
        //        using (IDisposable disposableEnumerator = (IDisposable)source.GetEnumerator())
        //        {
        //            IEnumerator enumerator = (IEnumerator)disposableEnumerator;
        //            while (enumerator.MoveNext())
        //            {
        //                num++;
        //            }
        //        }
        //    }
        //    else
        //    {
        //        IEnumerator enumerator = source.GetEnumerator();
        //        while (enumerator.MoveNext())
        //        {
        //            num++;
        //        }
        //    }
        //    return num;
        //}

        public static HashSet<T> ToHashSet<T>(this IEnumerable<T> source)
        {
            return new HashSet<T>(source);
        }

        public static HashSet<T> ToHashSet<T>(this IEnumerable<T> source, IEqualityComparer<T> comparer)
        {
            return new HashSet<T>(source, comparer);
        }

        public static IEnumerable<TResult> ZipX<TFirst, TSecond, TResult>(this IEnumerable<TFirst> first, IEnumerable<TSecond> second, Func<TFirst, TSecond, TResult> func)
        {
            var ie1 = first.GetEnumerator();
            var ie2 = second.GetEnumerator();

            while (ie1.MoveNext() && ie2.MoveNext())
                yield return func(ie1.Current, ie2.Current);
        }

        public static IEnumerable<TResult> ZipX<TFirst, TSecond, TResult>(this IEnumerable<TFirst> first, IEnumerable<TSecond> second, Func<TFirst, TSecond, int, TResult> func)
        {
            var ie1 = first.GetEnumerator();
            var ie2 = second.GetEnumerator();
            int index = 0;

            while (ie1.MoveNext() && ie2.MoveNext())
            {
                yield return func(ie1.Current, ie2.Current, index);
                index++;
            }
        }
    }
}

// ReSharper disable CheckNamespace
namespace System.Collections.Generic
// ReSharper restore CheckNamespace
{
	public static class ObjectAsEnumerableExtenstions //Based on namespace of IEnumerable<T>  (the return value)
	{
        [NotNull]
        public static IEnumerable<T> AsEnumerableX<T>(this T source) 
		{
			if (ReferenceEquals(source, null))
			{
				throw new ArgumentNullException();
			}
			yield return source;
		}

        [NotNull]
        public static IEnumerable<T> AsEnumerableEmptyIfNullX<T>(this T source)
		{
			if (ReferenceEquals(source, null))
			{
				return Enumerable.Empty<T>();
			}
			return source.AsEnumerableX();
		}
	}
}